package gee

import (
	"html/template"
	"log"
	"net/http"
	"path"
	"strings"
)

// HandlerFunc define the request handler used by gee
type HandlerFunc func(ctx *Context)

type RouterGroup struct {
	prefix      string
	middlewares []HandlerFunc
	engine      *Engine // all groups share an Engine instance
}

// Engine implements the interface of ServeHttp
type Engine struct {
	*RouterGroup
	router *router
	groups []*RouterGroup

	// templates
	htmlTemplates *template.Template
	funcMap       template.FuncMap
}

// New
// @Description: get the instance of gee Engine
// @return *Engine
func New() *Engine {
	engine := &Engine{router: newRouter()}
	engine.RouterGroup = &RouterGroup{engine: engine}
	engine.groups = []*RouterGroup{engine.RouterGroup}
	return engine
}

func Default() *Engine {
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

// Group
// @Description: create a new router group
// @receiver group *RouterGroup
// @param prefix string
// @return *RouterGroup
func (group *RouterGroup) Group(prefix string) *RouterGroup {
	engine := group.engine
	rGroup := &RouterGroup{
		prefix: prefix,
		engine: engine,
	}
	engine.groups = append(engine.groups, rGroup)
	return rGroup
}

func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
	group.middlewares = append(group.middlewares, middlewares...)
}

func (e *Engine) SetFuncMap(funcMap template.FuncMap) {
	e.funcMap = funcMap
}

func (e *Engine) LoadHTMLGlob(pattern string) {
	e.htmlTemplates = template.Must(
		// 使用函数映射去解析文件夹
		template.New("").Funcs(e.funcMap).ParseGlob(pattern),
	)
}

// createStaticHandler
// @Description: create static handler
// @receiver group *RouterGroup
// @param relativePath string  相对于root的目录,以/代表root
// @param fs http.FileSystem 设置root后的fileSystem
// @return HandlerFunc
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
	prefixedPath := path.Join(group.prefix, relativePath)
	fileHttpServer := http.StripPrefix(prefixedPath, http.FileServer(fs)) // relative + root

	return func(ctx *Context) {
		// manually check if file exists
		file := ctx.Param("filepath")
		if _, err := fs.Open(file); err != nil {
			ctx.Status(http.StatusNotFound)
			return
		}

		// 将文件写入http请求中,server自己解析request的path + FileServer(root)
		fileHttpServer.ServeHTTP(ctx.Writer, ctx.Request)
	}
}

func (group *RouterGroup) Static(relativePath string, root string) {
	handler := group.createStaticHandler(relativePath, http.Dir(root))
	urlPattern := path.Join(relativePath, "/*filepath")
	// register get handler
	group.GET(urlPattern, handler)
}

// addRoute
// @Description: add route to router container
// @receiver e *Engine
// @param method string
// @param pattern string
// @param handler HandlerFunc
func (group *RouterGroup) addRoute(method string, pattern string, handler HandlerFunc) {
	pattern = group.prefix + pattern
	group.engine.router.addRoute(method, pattern, handler)
}

// GET
// @Description: the interface to user to define a get route handler
// @receiver e
// @param pattern string
// @param handler HandlerFunc
func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
	group.addRoute(http.MethodGet, pattern, handler)
}

func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
	group.addRoute(http.MethodPost, pattern, handler)
}

// Run
// @Description: start the http server
// @receiver e
// @param addr string
// @return err
func (e *Engine) Run(addr string) (err error) {
	log.Printf("gee run at: http://%s%s\n", getServerAddr(), addr)
	return http.ListenAndServe(addr, e)
}

// ServeHTTP
// @Description: implements the server interface to solve the request
// @receiver e
// @param writer http.ResponseWriter
// @param request *http.Request
func (e *Engine) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	middlewares := make([]HandlerFunc, 0)
	for _, group := range e.groups {
		if strings.HasPrefix(request.URL.Path, group.prefix) {
			middlewares = append(middlewares, group.middlewares...)
		}
	}

	ctx := newContext(writer, request)
	ctx.handlers = middlewares
	ctx.engine = e
	e.router.handle(ctx)
}
